package net.ersiner.dmtools.csvmatricizer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;

/**
 * <p>A Tool for converting sparse-matrix like CSV files containing instance
 * representations as attribute tuples into full matrix form CSV files
 * where each unique attribute is represented by a column, and attribute
 * occurrences for each instance are represented with a 'T' character
 * (meaning "True").</p>
 * 
 * <p>Resultant format is mostly appropriate for Machine Learning Tools like
 * <a href="http://www.cs.waikato.ac.nz/ml/weka/">Weka.</a></p>
 * 
 * <p>A sample input, from a shopping basket, can be as follows:
 * 
 * <pre>
 *  Bread,Milk
 *  Bread,Diapers,Beer,Eggs
 *  Milk,Diapers,Beer,Cola
 *  Bread,Milk,Diapers,Beer
 *  Bread,Milk,Diapers,Cola
 * </pre>
 * 
 * Sample output according to the input above would be as follows:
 * 
 * <pre>
 *  Beer,Bread,Cola,Diapers,Eggs,Milk
 *  ,T,,,,T
 *  T,T,,T,T,
 *  T,,T,T,,T
 *  T,T,,T,,T
 *  ,T,T,T,,T
 * </pre>
 * 
 * Note that the attribute list in the output is sorted by name.
 * </p>
 * 
 * @author <a href="http://ersiner.net">Ersin Er</a>
 */
public class CsvMatricizer {
    
    private static final CsvMatricizer instance = new CsvMatricizer();
    
    private CsvMatricizer() {
        
    }
    
    
    /**
     * Returns the instance of {@link CsvMatricizer}.
     * 
     * @return The instance of {@link CsvMatricizer}.
     */
    public static CsvMatricizer getInstance() {
        return instance;
    }
    
    
    /**
     * Converts a sparse-matrix like CSV input into full matrix form CSV output.
     * 
     * @param input The input source.
     * @param output The output target.
     * @throws IOException
     */
    public void matricize(Readable input, Appendable output) throws IOException {
        
        List<String> attrs = new ArrayList<String>();
        List<List<String>> data = new ArrayList<List<String>>();
        
        Scanner fs = new Scanner(input);
        while(fs.hasNextLine()) {
            List<String> lineData = new ArrayList<String>();
            
            Scanner ls = new Scanner(fs.nextLine());
            ls.useDelimiter(",");
            while(ls.hasNext()) {
                String attr = ls.next().trim();
                lineData.add(attr);
                attrs.add(attr);
            }
            
            lineData = new ArrayList<String>(new HashSet<String>(lineData));
            Collections.sort(lineData);
            
            data.add(lineData);
        }
        
        fs.close();
        
        attrs = new ArrayList<String>(new HashSet<String>(attrs));
        Collections.sort(attrs);
        
        int c = 0;
        for (String attr : attrs) {
            output.append(attr);
            c++;
            if (c < attrs.size()) {
                output.append(",");
            }
            
        }
        output.append(System.getProperty("line.separator"));
        
        for (List<String> inst : data) {
            c = 0;
            int n = -1;
            int last = 0;
            
            for (String attr : inst) {
                n = attrs.indexOf(attr);
                
                for(int i = last; i < n; i++) {
                    c++;
                    output.append(",");
                }
                
                last = n + 1;
                output.append("T");
                if (c < attrs.size() - 1) {
                    c++;
                    output.append(",");
                }
            }
            
            for(int j = n + 1; j < attrs.size(); j++) {
                c++;
                if (c < attrs.size()) {
                    output.append(",");
                }
            }
            
            output.append(System.getProperty("line.separator"));
        }
    }

    
    /**
     * Entry point for using {@link CsvMatricizer} as an executable tool. Reads
     * from Standard Input and writes to Standard Output.
     * 
     * @param args N/A
     */
    public static void main(String[] args) {
        
        Reader input = new BufferedReader(new InputStreamReader(System.in));
        OutputStreamWriter output = new OutputStreamWriter(System.out);
        
        try {
            CsvMatricizer.getInstance().matricize(input, output);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                input.close();
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
